package app.species;

import sim.engine.*;
import sim.portrayal.*;
import sim.util.*;
import java.awt.*;
import java.awt.geom.*;


public abstract class Entity extends SimplePortrayal2D implements Steppable {

	/////////////////////////////////////////
	//// MUTABLE ATTRIBUTES
	//// either primitve or implements Mutable!
	/////////////////////////////////////////
	
	/**
	 * Lifespan is the amount of round an entity can live
	 */
	@MutableField(maxValue = 500000,minValue = 10000)
	protected int lifeSpan;
	
	/**
	 * diameter is the size of the creature
	 */
	@MutableField(relativeImpact=0.1f, probability=0.1f, maxValue = 1500,minValue = 200)
	protected double diameter;

	/**
	 * The distance, that the sensor can detect other life forms
	 */
	@MutableField(maxValue = 5000,minValue = 100)
	protected double senseDistance; // the distance the Entity can "see"
	
	/**
	 * these affect the way the entities are drawn
	 */	
	@MutableField(probability=0.05f,relativeImpact=0.17f,maxValue = 200,minValue = 0)
	protected MutableColor color;
	

	/**
	 * minimal speed of entity
	 */
	@MutableField(probability = 0.2f, relativeImpact = 0.1f, maxValue = 100, minValue = 0, defaultValue = 10)
	protected float minSpeed = 5;

	/**
	 * maximal speed of entity
	 */
	@MutableField(probability = 0.2f, relativeImpact = 0.1f, maxValue = 40, minValue = 0, defaultValue = 20)
	protected float maxSpeed = 20;


	///////////////////////////////////////////////////
	////  STATUS ATTRIBUTES
	///////////////////////////////////////////////////
	
	private int age;
	protected MutableDouble2D heading, position;
	private boolean alive;
	private double speed;
	private double acceleration;
	private int energy;

	///////////////////////////////////////////////////
	////  OTHER ATTRIBUTES
	///////////////////////////////////////////////////

	private boolean drawSensor, drawHeading;
	// not sure if keeping the stoppable object in here is a good idea
	protected Stoppable stoppable;

	
	/**
	 * 
	 */
	private static final long serialVersionUID = 2748527875603555807L;
	
	
	public Entity(MutableDouble2D initPos, MutableDouble2D initHead) {

		this.alive = true;
		this.age = 0;

		this.position = new MutableDouble2D(initPos);
		this.heading = new MutableDouble2D(initHead);
		this.drawSensor = false;
		this.drawHeading = false;

	}

	public Entity() {
		this(new MutableDouble2D(), new MutableDouble2D());
	}

	/**
	 * Do not overwrite this final method without a reason. 
	 */
	protected final void resetColor() {

		
		if(this instanceof MovingFood){
			this.color = MutableColor.green;
		}
		else if(this instanceof StationaryFood){
			this.color = MutableColor.red;
		}
		else {
			this.color = new MutableColor((int) (Math.random()*200.0),(int) (Math.random()*200.0),(int) (Math.random()*200.0));
		}
	}

	/* 
	 **/
	public void draw(Object object, Graphics2D graphics, DrawInfo2D info) {

		// get out of here if the thing is dead
		if (!this.alive)
			return;

		double width, height;
		int x, y, w, h;

		// draw a yellow circle around the Creature if it just mated
		if (this instanceof Creature) {
			Creature this_creature = (Creature) this;
			if (this_creature.justMated > 0) {

				width = info.draw.width * this.diameter * 3.0;
				height = info.draw.height * this.diameter * 3.0;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(Color.yellow);
				if(this_creature.getAge() < CreatureContext.SHOW_MATING_ALERT_TIME)
					graphics.setColor(new Color(0xFFFFCC));
				graphics.fillOval(x, y, w, h);
			}
			else if (this_creature.justMated < 0) {

				width = info.draw.width * this.diameter * 3.0;
				height = info.draw.height * this.diameter * 3.0;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(Color.green);
				if(this_creature.getAge() < CreatureContext.SHOW_MATING_ALERT_TIME)
					graphics.setColor(new Color(0xCCFFCC));
				graphics.fillOval(x, y, w, h);
			}
			
			if (this_creature.getLifeSpan()-this_creature.getAge()< 1000) {

				width = info.draw.width * this.diameter * 2.0;
				height = info.draw.height * this.diameter * 2.0;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(new Color(0xDD0000));
				graphics.fillOval(x, y, w, h);
			}
			if (this_creature.getEnergy()< 500) {

				width = info.draw.width * this.diameter * 1.5;
				height = info.draw.height * this.diameter * 1.5;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(Color.ORANGE);
				graphics.fillOval(x, y, w, h);
			}

			// draw a green circle around the Creature if it's one of the best
			// moving food eaters
			if (this_creature.isBestMovingFoodEater) {

				width = info.draw.width * this.diameter * 3.0;
				height = info.draw.height * this.diameter * 3.0;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(Color.green);
				graphics.drawOval(x, y, w, h);

			}

			// draw a red circle around the Creature if it's one of the best
			// stationary food eaters
			if (this_creature.isBestStationaryFoodEater) {

				width = info.draw.width * this.diameter * 2.9;
				height = info.draw.height * this.diameter * 2.9;
				x = (int) (info.draw.x - width / 2.0);
				y = (int) (info.draw.y - height / 2.0);
				w = (int) (width);
				h = (int) (height);

				// draw the sensor centered on the origin
				graphics.setColor(Color.red);
				graphics.drawOval(x, y, w, h);

			}

		}

		// values for the Entity
		width = info.draw.width * this.diameter;
		height = info.draw.height * this.diameter;
		x = (int) (info.draw.x - width / 2.0);
		y = (int) (info.draw.y - height / 2.0);
		w = (int) (width);
		h = (int) (height);

		// draw the AV centered on the origin
		//if (this instanceof Creature) {
		//	graphics.setColor(((Creature) this).speciesColor());
		//} else {
			graphics.setColor(this.color);
		//}
		graphics.fillOval(x, y, w, h);

		if (this.drawSensor) {

			width = info.draw.width * this.senseDistance * 2.0;
			height = info.draw.height * this.senseDistance * 2.0;
			x = (int) (info.draw.x - width / 2.0);
			y = (int) (info.draw.y - height / 2.0);
			w = (int) (width);
			h = (int) (height);

			// draw the sensor centered on the origin
			graphics.setColor(Color.red);
			graphics.drawOval(x, y, w, h);

			if (this instanceof Creature) {

				int xline = (int) (info.draw.x + this.heading.y
						* (info.draw.width * this.senseDistance));
				int yline = (int) (info.draw.y + (-this.heading.x)
						* (info.draw.height * this.senseDistance));
				MutableDouble2D segment = new MutableDouble2D(xline
						- info.draw.x, yline - info.draw.y);
				final double angle = Math.PI / CreatureContext.numVisionDivisions();

				for (int i = 0; i <= CreatureContext.numVisionDivisions(); i++) {

					graphics.drawLine((int) info.draw.x, (int) info.draw.y,
							(int) (segment.x + info.draw.x),
							(int) (segment.y + info.draw.y));

					segment.rotate(angle);

				}

			}

		}

		if (this.drawHeading) {

			// values for the direction
			final int xline, yline;

			xline = (int) (info.draw.x + this.heading.x
					* (info.draw.width * this.getRadius()));
			yline = (int) (info.draw.y + this.heading.y
					* (info.draw.height * this.getRadius()));

			// draw its direction
			graphics.setColor(Color.white);
			graphics.drawLine((int) info.draw.x, (int) info.draw.y, xline,
					yline);

		}
	}

	/*
	 * Each Entity in the simulation is a circle. This code was taken from the
	 * Ball.java file from Tutorial 5 as the authors of the tutorial should know
	 * how to write the most efficient code for this function.
	 */
	public boolean hitObject(Object object, DrawInfo2D range) {

		final double SLOP = 1.0; // need a little extra diameter to hit circles
		final double width = range.draw.width * this.getRadius();
		final double height = range.draw.height * this.diameter;

		Ellipse2D.Double ellipse = new Ellipse2D.Double(range.draw.x - width
				/ 2 - SLOP, range.draw.y - height / 2 - SLOP, width + SLOP * 2,
				height + SLOP * 2);
		return (ellipse.intersects(range.clip.x, range.clip.y,
				range.clip.width, range.clip.height));

	}

	// updates the position of the entity based on its current heading and speed
	public void updatePosition() {

		//use accelleration to compute speed
		this.speed += acceleration; 
		this.speed = this.speed * 0.95;
		// limit speed!
		if (speed < this.minSpeed)
			speed = minSpeed;
		else if (speed > maxSpeed)
			speed = maxSpeed;
		this.position.addIn(this.heading.x * this.speed, this.heading.y
				* this.speed);

	}

	// updates the heading of the entity if it is hitting a wall
	public void updateHeadingHitWall(int simWidth, int simHeight) {

		/*
		 * reverse direction if a wall is encountered
		 */
		if ((this.position.x - this.getRadius() <= 0.0 && this.heading.x < 0.0)
				|| (this.position.x + this.getRadius() >= simWidth && this.heading.x > 0.0)) {
			this.heading.x *= -1.0;
		}

		if ((this.position.y - this.getRadius() <= 0.0 && this.heading.y < 0.0)
				|| (this.position.y + this.getRadius() >= simHeight && this.heading.y > 0.0)) {
			this.heading.y *= -1.0;
		}

	}

	// returns true if the entity is hitting a wall
	public boolean hittingWall(int simWidth, int simHeight) {

		return (this.position.x - this.getRadius() <= 0.0 && this.heading.x < 0.0)
				|| (this.position.x + this.getRadius() >= simWidth && this.heading.x > 0.0)
				|| (this.position.y - this.getRadius() <= 0.0 && this.heading.y < 0.0)
				|| (this.position.y + this.getRadius() >= simHeight && this.heading.y > 0.0);

	}

	/*
	 * returns true if both entities are alive and they are touching
	 */
	public boolean isTouching(Entity that) {

		if (!this.alive || !that.alive)
			return false;

		return (this.position.distance(that.position) <= this.getRadius()
				+ that.getRadius());

	}

	// simply adds in the value to the entity's energy
	public void addEnergy(int n) {
		this.energy += n;
	}

	// setters and getters
	public boolean isAlive() {
		return this.alive;
	}

	public void setAlive(boolean b) {
		this.alive = b;
	}

	public int getEnergy() {
		return this.energy;
	}

	public void setEnergy(int n) {
		this.energy = n;
	}

	public double getDiameter() {
		return this.diameter;
	}

	public double getRadius() {
		return this.diameter * 0.5;
	}

	public void setDiameter(double d) {
		this.diameter = d;
	}

	public void setSenseDistance(double d) {
		this.senseDistance = d;
	}

	public double getSenseDistance() {
		return this.senseDistance;
	}

	public double getSpeed() {
		return this.speed;
	}
	
	public double getAcceleration(){
		return this.acceleration;
	}
	
	public void setAcceleration(double acc){
		this.acceleration= acc;
	}

	public void setSpeed(double d) {
		this.speed = d;
	}

	public void setLifeSpan(int n) {
		this.lifeSpan = n;
	}

	public int getLifeSpan() {
		return this.lifeSpan;
	}

	public void incrementAge() {
		this.age++;
	}

	public int getAge() {
		return this.age;
	}

	public void setAge(int n) {
		this.age = n;
	}

	public void setDrawSensor(boolean b) {
		this.drawSensor = b;
	}

	public boolean getDrawSensor() {
		return this.drawSensor;
	}

	public void setDrawHeading(boolean b) {
		this.drawHeading = b;
	}

	public boolean getDrawHeading() {
		return this.drawHeading;
	}
}
